#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <malloc.h>
#include <unistd.h>

#include <libusb.h>
#include "common.h"
#include "sha1.h"
#include "upgrade_ccm4201s.h"
#include "ofprint_common.h"


// Mass Storage Requests values. See section 3 of the Bulk-Only Mass Storage Class specifications
//#define BOMS_RESET                    0xFF
//#define BOMS_GET_MAX_LUN              0xFE

#define RETRY_MAX                     5
#define REQUEST_SENSE_LENGTH          0x12
#define INQUIRY_LENGTH                0x24
#define READ_CAPACITY_LENGTH          0x08

#define ERR_EXIT(errcode) do { D_ERR("   %s\n", libusb_strerror((enum libusb_error)errcode)); return -1; } while (0)
#define CALL_CHECK(fcall) do { int _r=fcall; if (_r < 0) ERR_EXIT(_r); } while (0)


// Section 5.1: Command Block Wrapper (CBW)
struct command_block_wrapper {
    uint8_t dCBWSignature[4];
    uint32_t dCBWTag;
    uint32_t dCBWDataTransferLength;
    uint8_t bmCBWFlags;
    uint8_t bCBWLUN;
    uint8_t bCBWCBLength;
    uint8_t CBWCB[16];
};

// Section 5.2: Command Status Wrapper (CSW)
struct command_status_wrapper {
    uint8_t dCSWSignature[4];
    uint32_t dCSWTag;
    uint32_t dCSWDataResidue;
    uint8_t bCSWStatus;
};


static int send_mass_storage_command(libusb_device_handle *handle, uint8_t endpoint, uint8_t lun,
    uint8_t *cdb, uint8_t direction, int data_length, uint32_t *ret_tag)
{
    static uint32_t tag = 1;
    uint8_t cdb_len;
    int i, r, size;
    struct command_block_wrapper cbw;

    if ((cdb == NULL)||(NULL == handle)) {
        return -1;
    }
    //WD_DBG("endpoint = 0x%02x\n", endpoint);

    if (endpoint & LIBUSB_ENDPOINT_IN) {
        WD_ERR("send_mass_storage_command: cannot send command on IN endpoint\n");
        return -1;
    }

    //cdb_len = cdb_length[cdb[0]];
    cdb_len = 16;
    if ((cdb_len == 0) || (cdb_len > sizeof(cbw.CBWCB))) {
        WD_ERR("send_mass_storage_command: don't know how to handle this command (%02X, length %d)\n",
            cdb[0], cdb_len);
        return -1;
    }

    memset(&cbw, 0, sizeof(cbw));
    cbw.dCBWSignature[0] = 'U';
    cbw.dCBWSignature[1] = 'S';
    cbw.dCBWSignature[2] = 'B';
    cbw.dCBWSignature[3] = 'C';
    *ret_tag = tag;
    cbw.dCBWTag = tag++;
    cbw.dCBWDataTransferLength = data_length;
    cbw.bmCBWFlags = direction;
    cbw.bCBWLUN = lun;
    // Subclass is 1 or 6 => cdb_len
    cbw.bCBWCBLength = cdb_len;
    memcpy(cbw.CBWCB, cdb, cdb_len);

    i = 0;
    do {
        // The transfer length must always be exactly 31 bytes.
        pthread_mutex_lock(&(dev_ofprint->usb_bulk_mutex));
        r = libusb_bulk_transfer(handle, endpoint, (unsigned char*)&cbw, 31, &size, 1000);
        if (r == LIBUSB_ERROR_PIPE) {
            libusb_clear_halt(handle, endpoint);
        }
        pthread_mutex_unlock(&(dev_ofprint->usb_bulk_mutex));
        i++;
    } while ((r == LIBUSB_ERROR_PIPE) && (i<RETRY_MAX));
    if (r != LIBUSB_SUCCESS) {
        WD_ERR("   send_mass_storage_command: %s\n", libusb_strerror((enum libusb_error)r));
        return -1;
    }

    D_DBG("   sent %d CDB bytes\n", cdb_len);
    return 0;
}

static int get_mass_storage_status(libusb_device_handle *handle, uint8_t endpoint, uint32_t expected_tag)
{
    int i, r, size;
    struct command_status_wrapper csw;

    // The device is allowed to STALL this transfer. If it does, you have to
    // clear the stall and try again.
    i = 0;
    do {
        pthread_mutex_lock(&(dev_ofprint->usb_bulk_mutex));
        r = libusb_bulk_transfer(handle, endpoint, (unsigned char*)&csw, 13, &size, 1000);
        if (r == LIBUSB_ERROR_PIPE) {
            libusb_clear_halt(handle, endpoint);
        }
        pthread_mutex_unlock(&(dev_ofprint->usb_bulk_mutex));
        i++;
    } while ((r == LIBUSB_ERROR_PIPE) && (i<RETRY_MAX));
    if (r != LIBUSB_SUCCESS) {
        WD_ERR("   get_mass_storage_status: %s\n", libusb_strerror((enum libusb_error)r));
        return -1;
    }
    if (size != 13) {
        WD_ERR("   get_mass_storage_status: received %d bytes (expected 13)\n", size);
        return -1;
    }
    if (csw.dCSWTag != expected_tag) {
        WD_ERR("   get_mass_storage_status: mismatched tags (expected %08X, received %08X)\n",
            expected_tag, csw.dCSWTag);
        return -1;
    }
    // For this test, we ignore the dCSWSignature check for validity...
    D_DBG("   Mass Storage Status: %02X (%s)\n", csw.bCSWStatus, csw.bCSWStatus?"FAILED":"Success");
    if (csw.dCSWTag != expected_tag)
        return -1;
    if (csw.bCSWStatus) {
        // REQUEST SENSE is appropriate only if bCSWStatus is 1, meaning that the
        // command failed somehow.  Larger values (2 in particular) mean that
        // the command couldn't be understood.
        if (csw.bCSWStatus == 1)
            return -2;  // request Get Sense
        else
            return -1;
    }

    // In theory we also should check dCSWDataResidue.  But lots of devices
    // set it wrongly.
    return 0;
}

static void get_sense(libusb_device_handle *handle, uint8_t endpoint_in, uint8_t endpoint_out)
{
    uint8_t cdb[16];    // SCSI Command Descriptor Block
    uint8_t sense[18];
    uint32_t expected_tag;
    int size;
    int rc;

    // Request Sense
    D_DBG("Request Sense:\n");
    memset(sense, 0, sizeof(sense));
    memset(cdb, 0, sizeof(cdb));
    cdb[0] = 0x03;  // Request Sense
    cdb[4] = REQUEST_SENSE_LENGTH;

    send_mass_storage_command(handle, endpoint_out, 0, cdb, LIBUSB_ENDPOINT_IN, REQUEST_SENSE_LENGTH, &expected_tag);
    pthread_mutex_lock(&(dev_ofprint->usb_bulk_mutex));
    rc = libusb_bulk_transfer(handle, endpoint_in, (unsigned char*)&sense, REQUEST_SENSE_LENGTH, &size, 1000);
    pthread_mutex_unlock(&(dev_ofprint->usb_bulk_mutex));
    if (rc < 0)
    {
        WD_ERR("libusb_bulk_transfer failed: %s\n", libusb_error_name(rc));
        return;
    }
    D_DBG("   received %d bytes\n", size);

    if ((sense[0] != 0x70) && (sense[0] != 0x71)) {
        WD_ERR("   ERROR No sense data\n");
    } else {
        WD_ERR("   ERROR Sense: %02X %02X %02X\n", sense[2]&0x0F, sense[12], sense[13]);
    }
    // Strictly speaking, the get_mass_storage_status() call should come
    // before these perr() lines.  If the status is nonzero then we must
    // assume there's no data in the buffer.  For xusb it doesn't matter.
    get_mass_storage_status(handle, endpoint_in, expected_tag);
}


static int32 ccm4201s_get_boot_version(CCM4201S_UPGRADE_PARAM *this)
{
    libusb_device_handle *handle = this->usb_handle.handle;
    uint8 bInEndpointAddress = this->usb_handle.endpoint_in;
    uint8 bOutEndpointAddress = this->usb_handle.endpoint_out;

    uint8 cdb[16] = {0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00};
    uint32_t expected_tag;
    int size;
    uint8_t lun = 0;
    uint8_t buffer[64];

#if 0
    printf("Reading Max LUN:\n");
    r = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE,
        BOMS_GET_MAX_LUN, 0, 0, &lun, 1, 1000);
    // Some devices send a STALL instead of the actual value.
    // In such cases we should set lun to 0.
    if (r == 0) {
        lun = 0;
    } else if (r < 0) {
        D_ERR("   Failed: %s", libusb_strerror((enum libusb_error)r));
    }
    printf("   Max LUN = %d\n", lun);
#endif
    WD_DBG("bInEndpointAddress = 0x%02x, bOutEndpointAddress = 0x%02x\n", bInEndpointAddress, bOutEndpointAddress);
    if(NULL == handle)
    {
        WD_ERR("usb device not opened!\n");
        return -1;
    }
    if(0  != send_mass_storage_command(handle, bOutEndpointAddress, lun, cdb, LIBUSB_ENDPOINT_IN, INQUIRY_LENGTH, &expected_tag))
    {
        D_ERR("read boot version: FAIL!!!\n");
        return -1;
    }
    pthread_mutex_lock(&(dev_ofprint->usb_bulk_mutex));
    CALL_CHECK(libusb_bulk_transfer(handle, bInEndpointAddress, (unsigned char*)&buffer, INQUIRY_LENGTH, &size, 1000));
    pthread_mutex_unlock(&(dev_ofprint->usb_bulk_mutex));
    if (get_mass_storage_status(handle, bInEndpointAddress, expected_tag) == -2) {
        get_sense(handle, bInEndpointAddress, bOutEndpointAddress);
        WD_ERR("get_mass_storage_status: FAIL!!!\n");
        return -1;
    }

    if(64 > strlen((char *)buffer))
    {
        strcpy(this->ccm4201s_boot_version, (char *)buffer);
        D_DBG("ccm4201s_boot_version:%s\n", this->ccm4201s_boot_version);
    }

    return 0;
}


static int32 ccm4201s_config_enable_verify(CCM4201S_UPGRADE_PARAM *this, uint32 addr, uint8 para1, uint32 para4)
{
    libusb_device_handle *handle = this->usb_handle.handle;
    uint8 bInEndpointAddress = this->usb_handle.endpoint_in;
    uint8 bOutEndpointAddress = this->usb_handle.endpoint_out;

    //uint8 buffer[64];
    uint8 cdb[16] = {0xdc, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00};
    uint8_t lun = 0;
    uint32_t expected_tag;
    int size;
    uint8 read_data[1024] = {0};

    cdb[1] = addr & 0xff;
    cdb[2] = (addr>>8) & 0xff;
    cdb[3] = (addr>>16) & 0xff;
    cdb[4] = (addr>>24) & 0xff;

    cdb[9] = para4 & 0xff;
    cdb[10] = (para4>>8) & 0xff;
    cdb[11] = (para4>>16) & 0xff;
    cdb[12] = (para4>>24) & 0xff;


    cdb[14] = para1;

    if(0  != send_mass_storage_command(handle, bOutEndpointAddress, lun, cdb, LIBUSB_ENDPOINT_IN, 512, &expected_tag))
    {
        WD_ERR("send_mass_storage_command: FAIL!!!\n");
        return -1;
    }
    pthread_mutex_lock(&(dev_ofprint->usb_bulk_mutex));
    CALL_CHECK(libusb_bulk_transfer(handle, bInEndpointAddress, (unsigned char*)&read_data, 512, &size, 1000));
    pthread_mutex_unlock(&(dev_ofprint->usb_bulk_mutex));

    if (get_mass_storage_status(handle, bInEndpointAddress, expected_tag) == -2) {
        get_sense(handle, bInEndpointAddress, bOutEndpointAddress);
        WD_ERR("get_mass_storage_status: FAIL!!!\n");
        return -1;
    }

    return 0;
}


static int32 ccm4201s_write_first_memory_package(CCM4201S_UPGRADE_PARAM *this)
{
    libusb_device_handle *handle = this->usb_handle.handle;
    uint8 bInEndpointAddress = this->usb_handle.endpoint_in;
    uint8 bOutEndpointAddress = this->usb_handle.endpoint_out;

    uint8 cdb[16] = {0xdc, 0xbc, 0x04, 0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00};
    uint8_t lun = 0;
    uint32_t expected_tag;
    int size;
    uint8 write_data[512] = {0x66, 0x99, 0x38, 0xc0, 0xeb, 0x0c, 0x06, 0x05, 0x35, 0x31, 0x32, 0x20, 0x52, 0xd8, 0x00, 0x00,\
                            0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x21, 0x04, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00};

    if(0  != send_mass_storage_command(handle, bOutEndpointAddress, lun, cdb, LIBUSB_ENDPOINT_OUT, 512, &expected_tag))
    {
        D_ERR("read boot version: FAIL!!!\n");
        return -1;
    }
    pthread_mutex_lock(&(dev_ofprint->usb_bulk_mutex));
    CALL_CHECK(libusb_bulk_transfer(handle, bOutEndpointAddress, (unsigned char*)&write_data, 512, &size, 1000));
    pthread_mutex_unlock(&(dev_ofprint->usb_bulk_mutex));

    if (get_mass_storage_status(handle, bInEndpointAddress, expected_tag) == -2) {
        get_sense(handle, bInEndpointAddress, bOutEndpointAddress);
        WD_ERR("get_mass_storage_status: FAIL!!!\n");
        return -1;
    }

    ccm4201s_config_enable_verify(this, 0x40003824, 1, 0);

    ccm4201s_config_enable_verify(this, 0x40003824, 2, 0);

    ccm4201s_config_enable_verify(this, 0x4000382c, 1, 0);

    ccm4201s_config_enable_verify(this, 0x4000382c, 2, 0x01f00000);

    return 0;
}

static int32 ccm4201s_page_erase(CCM4201S_UPGRADE_PARAM *this, uint32 addr, uint32 page_num)
{
    libusb_device_handle *handle = this->usb_handle.handle;
    uint8 bInEndpointAddress = this->usb_handle.endpoint_in;
    uint8 bOutEndpointAddress = this->usb_handle.endpoint_out;

    uint8 cdb[16] = {0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00};
    uint8_t lun = 0;
    uint32_t expected_tag;

    cdb[1] = addr & 0xff;
    cdb[2] = (addr>>8) & 0xff;
    cdb[3] = (addr>>16) & 0xff;
    cdb[4] = (addr>>24) & 0xff;

    cdb[5] = page_num & 0xff;
    cdb[6] = (page_num>>8) & 0xff;
    cdb[7] = (page_num>>16) & 0xff;
    cdb[8] = (page_num>>24) & 0xff;


    if(0  != send_mass_storage_command(handle, bOutEndpointAddress, lun, cdb, LIBUSB_ENDPOINT_OUT, 0, &expected_tag))
    {
        WD_ERR("send_mass_storage_command: FAIL!!!\n");
        return -1;
    }

    if (get_mass_storage_status(handle, bInEndpointAddress, expected_tag) == -2) {
        get_sense(handle, bInEndpointAddress, bOutEndpointAddress);
        WD_ERR("get_mass_storage_status: FAIL!!!\n");
        return -1;
    }

    return 0;
}


static int32 ccm4201s_write_sram(CCM4201S_UPGRADE_PARAM *this, uint8 *data, int len, int data_len)
{
    libusb_device_handle *handle = this->usb_handle.handle;
    uint8 bInEndpointAddress = this->usb_handle.endpoint_in;
    uint8 bOutEndpointAddress = this->usb_handle.endpoint_out;

    uint8 cdb[16] = {0xdc, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00};
    uint8_t lun = 0;
    uint32_t expected_tag;
    int size;

    cdb[5] = data_len & 0xff;
    cdb[6] = (data_len>>8) & 0xff;
    cdb[7] = (data_len>>16) & 0xff;
    cdb[8] = (data_len>>24) & 0xff;

    if(0  != send_mass_storage_command(handle, bOutEndpointAddress, lun, cdb, LIBUSB_ENDPOINT_OUT, len, &expected_tag))
    {
        WD_ERR("read boot version: FAIL!!!\n");
        return -1;
    }
    pthread_mutex_lock(&(dev_ofprint->usb_bulk_mutex));
    CALL_CHECK(libusb_bulk_transfer(handle, bOutEndpointAddress, (unsigned char*)data, len, &size, 3000));
    pthread_mutex_unlock(&(dev_ofprint->usb_bulk_mutex));
    if (get_mass_storage_status(handle, bInEndpointAddress, expected_tag) == -2) {
        get_sense(handle, bInEndpointAddress, bOutEndpointAddress);
        WD_ERR("get_mass_storage_status: FAIL!!!\n");
        return -1;
    }

    return 0;
}


static int32 ccm4201s_write_flash(CCM4201S_UPGRADE_PARAM *this, uint32 addr, int len)
{
    libusb_device_handle *handle = this->usb_handle.handle;
    uint8 bInEndpointAddress = this->usb_handle.endpoint_in;
    uint8 bOutEndpointAddress = this->usb_handle.endpoint_out;

    uint8 cdb[16] = {0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x04, 0x00, 0x00};
    uint8_t lun = 0;
    uint32_t expected_tag;

    cdb[1] = addr & 0xff;
    cdb[2] = (addr>>8) & 0xff;
    cdb[3] = (addr>>16) & 0xff;
    cdb[4] = (addr>>24) & 0xff;

    cdb[5] = len & 0xff;
    cdb[6] = (len>>8) & 0xff;
    cdb[7] = (len>>16) & 0xff;
    cdb[8] = (len>>24) & 0xff;

    if(0  != send_mass_storage_command(handle, bOutEndpointAddress, lun, cdb, LIBUSB_ENDPOINT_OUT, 0, &expected_tag))
    {
        WD_ERR("send_mass_storage_command: FAIL!!!\n");
        return -1;
    }

    if (get_mass_storage_status(handle, bInEndpointAddress, expected_tag) == -2) {
        get_sense(handle, bInEndpointAddress, bOutEndpointAddress);
        WD_ERR("get_mass_storage_status: FAIL!!!\n");
        return -1;
    }

    return 0;
}

static int32 ccm4201s_sha_verify(CCM4201S_UPGRADE_PARAM *this, int32 addr, int32 len, uint8 *sha_result)
{
    libusb_device_handle *handle = this->usb_handle.handle;
    uint8 bInEndpointAddress = this->usb_handle.endpoint_in;
    uint8 bOutEndpointAddress = this->usb_handle.endpoint_out;

    uint8 cdb[16] = {0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x20, 0x00};
    uint32_t expected_tag;
    int size;
    uint8_t lun = 0;

    cdb[1] = addr & 0xff;
    cdb[2] = (addr>>8) & 0xff;
    cdb[3] = (addr>>16) & 0xff;
    cdb[4] = (addr>>24) & 0xff;

    cdb[5] = len & 0xff;
    cdb[6] = (len>>8) & 0xff;
    cdb[7] = (len>>16) & 0xff;
    cdb[8] = (len>>24) & 0xff;

    if(0  != send_mass_storage_command(handle, bOutEndpointAddress, lun, cdb, LIBUSB_ENDPOINT_IN, 20, &expected_tag))
    {
        D_ERR("read boot version: FAIL!!!\n");
        return -1;
    }
    pthread_mutex_lock(&(dev_ofprint->usb_bulk_mutex));
    CALL_CHECK(libusb_bulk_transfer(handle, bInEndpointAddress, (unsigned char*)sha_result, 20, &size, 1000));
    pthread_mutex_unlock(&(dev_ofprint->usb_bulk_mutex));

    if (get_mass_storage_status(handle, bInEndpointAddress, expected_tag) == -2) {
        get_sense(handle, bInEndpointAddress, bOutEndpointAddress);
        WD_ERR("get_mass_storage_status: FAIL!!!\n");
        return -1;
    }

    return 0;
}


static int32 ccm4201s_write_last_memory_package(CCM4201S_UPGRADE_PARAM *this)
{
    libusb_device_handle *handle = this->usb_handle.handle;
    uint8 bInEndpointAddress = this->usb_handle.endpoint_in;
    uint8 bOutEndpointAddress = this->usb_handle.endpoint_out;

    uint8 cdb[16] = {0xdc, 0x00, 0x30, 0x00, 0x20, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00};
    uint8_t lun = 0;
    uint32_t expected_tag;
    int size;
    uint8 write_data[512] = {0x00, 0x10, 0x00, 0x10};

    if(0  != send_mass_storage_command(handle, bOutEndpointAddress, lun, cdb, LIBUSB_ENDPOINT_OUT, 512, &expected_tag))
    {
        D_ERR("read boot version: FAIL!!!\n");
        return -1;
    }
    pthread_mutex_lock(&(dev_ofprint->usb_bulk_mutex));
    CALL_CHECK(libusb_bulk_transfer(handle, bOutEndpointAddress, (unsigned char*)&write_data, 512, &size, 1000));
    pthread_mutex_unlock(&(dev_ofprint->usb_bulk_mutex));

    if (get_mass_storage_status(handle, bInEndpointAddress, expected_tag) == -2) {
        get_sense(handle, bInEndpointAddress, bOutEndpointAddress);
        WD_ERR("get_mass_storage_status: FAIL!!!\n");
        return -1;
    }

    return 0;
}


static int32 ccm4201s_disable_bootloader(CCM4201S_UPGRADE_PARAM *this)
{
    libusb_device_handle *handle = this->usb_handle.handle;
    uint8 bInEndpointAddress = this->usb_handle.endpoint_in;
    uint8 bOutEndpointAddress = this->usb_handle.endpoint_out;

    uint8 cdb[16] = {0xdc, 0x00, 0x20, 0x00, 0x20, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00};
    uint8_t lun = 0;
    uint32_t expected_tag;
    int size;
    uint8 write_data[512] = {0x2d, 0xe9, 0xf0, 0x41, 0x90, 0xb0, 0x32, 0x48, 0x02, 0x90, 0x32, 0x48, 0x03, 0x90, 0x00, 0x14,
                             0x31, 0x49, 0x32, 0x4c, 0x04, 0x90, 0xcd, 0xe9, 0x05, 0x01, 0x21, 0x68, 0x07, 0x91, 0x30, 0x49,
                             0x08, 0x91, 0x07, 0x99, 0xb1, 0xf1, 0x10, 0x2f, 0x02, 0xd2, 0x4f, 0xf0, 0x11, 0x30, 0x04, 0xe0,
                             0xb1, 0xf1, 0x81, 0x5f, 0x03, 0xd3, 0x4f, 0xf0, 0x99, 0x30, 0x20, 0x60, 0x31, 0xe0, 0x09, 0x05,
                             0x02, 0xd0, 0x4f, 0xf0, 0xaa, 0x30, 0xf8, 0xe7, 0x4f, 0xf0, 0x80, 0x58, 0x0b, 0x90, 0x0c, 0x90,
                             0x44, 0xf6, 0x19, 0x27, 0x42, 0xf6, 0x61, 0x71, 0x42, 0xf6, 0x05, 0x76, 0x45, 0x46, 0x40, 0x46,
                             0x88, 0x47, 0x07, 0xaa, 0x01, 0x21, 0x1f, 0x48, 0xb0, 0x47, 0x02, 0xaa, 0x05, 0x21, 0x1e, 0x48,
                             0xb0, 0x47, 0x28, 0x46, 0xb8, 0x47, 0xd5, 0xf8, 0x60, 0x01, 0x09, 0x90, 0xd5, 0xf8, 0x64, 0x01,
                             0x0a, 0x90, 0xd5, 0xf8, 0x70, 0x01, 0x0d, 0x90, 0x28, 0x6a, 0x0e, 0x90, 0x17, 0x4d, 0x00, 0x20,
                             0x02, 0xa9, 0x09, 0xaa, 0x51, 0xf8, 0x20, 0xc0, 0x52, 0xf8, 0x20, 0x30, 0x63, 0x45, 0x04, 0xd0,
                             0x25, 0x60, 0x10, 0xb0, 0x00, 0x20, 0xbd, 0xe8, 0xf0, 0x81, 0x40, 0x1c, 0x06, 0x28, 0xf1, 0xd3,
                             0x01, 0xf1, 0x18, 0x02, 0x01, 0x21, 0x40, 0x46, 0xb0, 0x47, 0x40, 0x46, 0xb8, 0x47, 0xd8, 0xf8,
                             0x00, 0x00, 0x08, 0x99, 0x0f, 0x90, 0x81, 0x42, 0xea, 0xd1, 0x09, 0x48, 0xb5, 0xe7, 0x00, 0x00,
                             0x66, 0x99, 0x38, 0xc0, 0xeb, 0x0c, 0xff, 0xff, 0x02, 0x02, 0x21, 0x04, 0x00, 0x30, 0x00, 0x20,
                             0x21, 0x37, 0xec, 0x3a, 0x20, 0x00, 0x00, 0x10, 0x60, 0x01, 0x00, 0x10, 0x78, 0x56, 0x34, 0x12,
                             0xaa, 0x55, 0xaa, 0x55};

    if(0  != send_mass_storage_command(handle, bOutEndpointAddress, lun, cdb, LIBUSB_ENDPOINT_OUT, 512, &expected_tag))
    {
        WD_ERR("send_mass_storage_command: FAIL!!!\n");
        return -1;
    }
    pthread_mutex_lock(&(dev_ofprint->usb_bulk_mutex));
    CALL_CHECK(libusb_bulk_transfer(handle, bOutEndpointAddress, (unsigned char*)&write_data, 512, &size, 1000));
    pthread_mutex_unlock(&(dev_ofprint->usb_bulk_mutex));

    if (get_mass_storage_status(handle, bInEndpointAddress, expected_tag) == -2) {
        get_sense(handle, bInEndpointAddress, bOutEndpointAddress);
        WD_ERR("get_mass_storage_status: FAIL!!!\n");
        return -1;
    }

    return 0;
}


static int32 ccm4201s_config_enable(CCM4201S_UPGRADE_PARAM *this)
{
    libusb_device_handle *handle = this->usb_handle.handle;
    uint8 bInEndpointAddress = this->usb_handle.endpoint_in;
    uint8 bOutEndpointAddress = this->usb_handle.endpoint_out;

    uint8 cdb[16] = {0xdc, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00};
    uint8_t lun = 0;
    uint32_t expected_tag;

    if(0  != send_mass_storage_command(handle, bOutEndpointAddress, lun, cdb, LIBUSB_ENDPOINT_OUT, 0, &expected_tag))
    {
        WD_ERR("send_mass_storage_command: FAIL!!!\n");
        return -1;
    }

    if (get_mass_storage_status(handle, bInEndpointAddress, expected_tag) == -2) {
        get_sense(handle, bInEndpointAddress, bOutEndpointAddress);
        WD_ERR("get_mass_storage_status: FAIL!!!\n");
        return -1;
    }

    return 0;
}

static int32 ccm4201s_disable_bootloader_verify(CCM4201S_UPGRADE_PARAM *this)
{
    libusb_device_handle *handle = this->usb_handle.handle;
    uint8 bInEndpointAddress = this->usb_handle.endpoint_in;
    uint8 bOutEndpointAddress = this->usb_handle.endpoint_out;

    uint8 cdb[16] = {0xdc, 0x00, 0x30, 0x00, 0x20, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00};
    uint8_t lun = 0;
    uint32_t expected_tag;
    int size;
    uint8 read_data[1024] = {0};

    if(0  != send_mass_storage_command(handle, bOutEndpointAddress, lun, cdb, LIBUSB_ENDPOINT_IN, 512, &expected_tag))
    {
        D_ERR("send_mass_storage_command: FAIL!!!\n");
        return -1;
    }
    pthread_mutex_lock(&(dev_ofprint->usb_bulk_mutex));
    CALL_CHECK(libusb_bulk_transfer(handle, bInEndpointAddress, (unsigned char*)&read_data, 512, &size, 1000));
    pthread_mutex_unlock(&(dev_ofprint->usb_bulk_mutex));

    if (get_mass_storage_status(handle, bInEndpointAddress, expected_tag) == -2) {
        get_sense(handle, bInEndpointAddress, bOutEndpointAddress);
        WD_ERR("get_mass_storage_status: FAIL!!!\n");
        return -1;
    }

    if((0xaa != read_data[0]) || (0x55 != read_data[1]) || (0xaa != read_data[2]) || (0x55 != read_data[3]))
    {
        WD_ERR("FAIL!!!\n");
        return -1;
    }

    return 0;
}

static int32 ccm4201s_soft_reset(CCM4201S_UPGRADE_PARAM *this)
{
    libusb_device_handle *handle = this->usb_handle.handle;
    //uint8 bInEndpointAddress = this->usb_handle.endpoint_in;
    uint8 bOutEndpointAddress = this->usb_handle.endpoint_out;

    uint8 cdb[16] = {0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdd, 0x00, 0x00};
    uint8_t lun = 0;
    uint32_t expected_tag;

    if(0  != send_mass_storage_command(handle, bOutEndpointAddress, lun, cdb, LIBUSB_ENDPOINT_OUT, 0, &expected_tag))
    {
        WD_ERR("send_mass_storage_command: FAIL!!!\n");
        return -1;
    }

#if 0
    if (get_mass_storage_status(handle, this->udev.bInEndpointAddress, expected_tag) == -2) {
        get_sense(handle, this->udev.bInEndpointAddress, INT_SEND_EP);
        return -1;
    }
#endif

    return 0;
}


int ccm4201s_upgrade_and_run(CCM4201S_UPGRADE_PARAM *this, char *file_path)
{
    FILE *file;
    int file_size = 0;
    int read_size = 0;
    int ret = -1;

    if((NULL == this) || (NULL == file_path))
    {
        WD_ERR("the param is ERROR!!!\n");
        ret = -2;
        goto END;
    }

    if(NULL == strstr(file_path, CCM4201S_FW_V))
    {
        WD_ERR("file_path = %s, ERROR!\n", file_path);
        ret = -2;
        goto END;
    }

    file_size = com_get_file_size(file_path);
    if((CCM4201S_FW_FILE_SIZE_MIN >= file_size) || (CCM4201S_FW_FILE_SIZE_MAX < file_size))//此处最大文件大小待确认
    {
        WD_ERR("%s file_size = %d, error!\n", file_path, file_size);
        ret = -2;
        goto END;
    }
    D_DBG("file_size = %d\n", file_size);

    file = fopen(file_path, "rb");
    if(NULL == file)
    {
        WD_ERR("%s open failed!\n", file_path);
        ret = -2;//文件未打开则直接取消升级，modified by hcq, 20230425
        goto END;
    }

    int code_size = 0;
    if(0 == file_size%512)
    {
        code_size = file_size;
    }
    else
    {
        int xn = file_size/512;
        code_size = (xn + 1) * 512;
    }
    D_DBG("code_size = %d\n", code_size);

    uint8 *file_data = (uint8 *)calloc(1, code_size);
    if(NULL == file_data)
    {
        WD_ERR("calloc file_data: FAIL!\n");
        goto FILE_OPEN_SUCCESS;
    }

    read_size = fread(file_data, 1, file_size, file);
    D_DBG("read_size = %d\n", read_size);
    if(file_size != read_size)
    {
        WD_ERR("file_size = %d, read_size = %d, read file failed!\n", file_size, read_size);
        goto CALLOC_FILE_DATA_SUCCESS;
    }

    char *data = (char *)file_data;
    int addr = 0;
    int size = file_size;
    SHA1Context sha;

    if (0 != SHA1Reset(&sha))
    {
        WD_ERR("SHA1Reset Error!\n");
        goto CALLOC_FILE_DATA_SUCCESS;
    }

    if(0 != SHA1Input(&sha, (uint8 *)data, size))
    {
        WD_ERR("SHA1Input: FAIL!\n");
        goto CALLOC_FILE_DATA_SUCCESS;
    }

    if(0 != SHA1Result(&sha, this->sha1_result))
    {
        WD_ERR("SHA1Result: FAIL!\n");
        goto CALLOC_FILE_DATA_SUCCESS;
    }

#if 0
    D_DBG("sha1_result:\n");
    for(int i = 0; i < 20 ; ++i)
    {
        printf("%02X ", (this->sha1_result)[i]);
    }
    printf("\n");
#endif

    WD_LOG("Please wait for upgrade firmware...\n");
    WD_LOG("升级固件中，请等待...\n");

    WD_DBG("vid:pid = %04x:%04x\n", this->usb_id.idVendor, this->usb_id.idProduct);
    if(DRV_OK != intf_usb_open(&(this->usb_id), &(this->usb_handle)))
    {
        WD_ERR("intf_usb_open: FAIL!\n");
        goto CALLOC_FILE_DATA_SUCCESS;
    }

    int erase_page_count = 0;
    if(0 == (file_size % CCM4201S_DOWNLOAD_PAGE_LEN))
    {
        erase_page_count = file_size/CCM4201S_DOWNLOAD_PAGE_LEN;
    }
    else
    {
        erase_page_count = file_size/CCM4201S_DOWNLOAD_PAGE_LEN + 1;
    }

    if(0 != ccm4201s_get_boot_version(this))
    {
        WD_ERR("ccm4201s_get_boot_version: FAIL!!!\n");
        goto INTF_USB_OPEN_SUCCESS;
    }

    if(0 != ccm4201s_write_first_memory_package(this))
    {
        WD_ERR("ccm4201s_write_first_memory_package: FAIL!!!\n");
        goto INTF_USB_OPEN_SUCCESS;
    }

    if(0 != ccm4201s_page_erase(this, CCM4201S_FLASH_BASE_ADDR, erase_page_count))
    {
        WD_ERR("ccm4201s_page_erase: FAIL!!!\n");
        goto INTF_USB_OPEN_SUCCESS;
    }

    int count = code_size/CCM4201S_DOWNLOAD_DATA_LEN_MAX;
    for(int i = 0; i < count; i++)
    {
        WD_DBG("write addr 0x%08x, data len %d:\n", (uint32)addr, CCM4201S_DOWNLOAD_DATA_LEN_MAX);
        if(0 != ccm4201s_write_sram(this, (uint8 *)(data+addr), CCM4201S_DOWNLOAD_DATA_LEN_MAX, CCM4201S_DOWNLOAD_DATA_LEN_MAX))
        {
            WD_ERR("ccm4201s_write_sram: FAIL!!!\n");
            goto INTF_USB_OPEN_SUCCESS;

        }

        if(0 != ccm4201s_write_flash(this, CCM4201S_FLASH_BASE_ADDR+addr, CCM4201S_DOWNLOAD_DATA_LEN_MAX))
        {
            WD_ERR("ccm4201s_write_flash: FAIL!!!\n");
            goto INTF_USB_OPEN_SUCCESS;
        }

        addr += CCM4201S_DOWNLOAD_DATA_LEN_MAX;
    }

    int32 rx = code_size%CCM4201S_DOWNLOAD_DATA_LEN_MAX;

    if(0 < rx)
    {
        D_DBG("write addr 0x%08x, data len %d:\n", (uint32)addr, rx);
        if(0 != ccm4201s_write_sram(this, (uint8 *)(data+addr), rx, file_size%CCM4201S_DOWNLOAD_DATA_LEN_MAX))
        {
            WD_LOG("ccm4201s_write_sram: FAIL!!!\n");
            goto INTF_USB_OPEN_SUCCESS;

        }
        if(0 != ccm4201s_write_flash(this, CCM4201S_FLASH_BASE_ADDR+addr, file_size%CCM4201S_DOWNLOAD_DATA_LEN_MAX))
        {
            goto INTF_USB_OPEN_SUCCESS;
        }
    }

    if(0 != ccm4201s_config_enable_verify(this, 0x40003824, 2, 0))
    {
        WD_ERR("ccm4201s_config_enable_verify: FAIL!!!\n");
        goto INTF_USB_OPEN_SUCCESS;
    }

    uint8 sha_value[100] = {0};
    if(0 != ccm4201s_sha_verify(this, CCM4201S_FLASH_BASE_ADDR, file_size, sha_value))
    {
        WD_ERR("ccm4201s_sha_verify: FAIL!!!\n");
        goto INTF_USB_OPEN_SUCCESS;
    }

    if(0 != com_compare_array(sha_value, this->sha1_result, 20))
    {
        WD_ERR("sha_compare: FAIL!!!\n");
        goto INTF_USB_OPEN_SUCCESS;
    }

    if(0 != ccm4201s_write_last_memory_package(this))
    {
        WD_ERR("ccm4201s_write_last_memory_package: FAIL!!!\n");
        goto INTF_USB_OPEN_SUCCESS;
    }

    if(0 != ccm4201s_disable_bootloader(this))
    {
        WD_ERR("ccm4201s_disable_bootloader: FAIL!!!\n");
        goto INTF_USB_OPEN_SUCCESS;
    }

    if(0 != ccm4201s_config_enable(this))
    {
        WD_ERR("ccm4201s_config_enable: FAIL!!!\n");
        goto INTF_USB_OPEN_SUCCESS;
    }

    if(0 != ccm4201s_disable_bootloader_verify(this))
    {
        WD_ERR("ccm4201s_disable_bootloader_verify: FAIL!!!\n");
        goto INTF_USB_OPEN_SUCCESS;
    }

    D_DBG("soft reset\n");
    ccm4201s_soft_reset(this);
    ret = 0;

INTF_USB_OPEN_SUCCESS:
    intf_usb_close(&(this->usb_handle));
CALLOC_FILE_DATA_SUCCESS:
    free(file_data);
FILE_OPEN_SUCCESS:
    fclose(file);
END:
    D_DBG("upgrade_and_run end.\n");
    return ret;
}



int32 ccm4201s_upgrade_module_init(CCM4201S_UPGRADE_PARAM *this)
{
    if(NULL == this)
    {
        WD_ERR("the param is ERROR!\n");
        return DRV_ERR;
    }

    memset((uint8 *)this, 0x00, sizeof(CCM4201S_UPGRADE_PARAM));
    this->usb_handle.handle = NULL;
    this->usb_id.idVendor = CCM4201S_BOOT_USB_VID;
    this->usb_id.idProduct = CCM4201S_BOOT_USB_PID;

    return DRV_OK;
}


